﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Practices.Prism.ViewModel;

namespace ManagerClient
{
    public abstract class ViewModelBase : NotificationObject, IDataErrorInfo
    {
        private static readonly Dictionary<string, object> PropertyLookupCache =
            new Dictionary<string, object>();

        #region IDataErrorInfo Members

        string IDataErrorInfo.Error
        {
            get { return null; }
        }

        string IDataErrorInfo.this[string columnName]
        {
            get { return ValidateProperty(columnName); }
        }

        #endregion

        private bool isBusy;
        public bool IsBusy
        {
            get { return isBusy; }
            set
            {
                isBusy = value;
                RaisePropertyChanged(() => IsBusy);
            }
        }
        protected virtual string ValidateProperty(string columnName)
        {
            // get cached property accessors
            Dictionary<string, Func<object, object>> propertyGetters = GetPropertyGetterLookups(GetType());

            if (propertyGetters.ContainsKey(columnName))
            {
                // read value of given property
                object value = propertyGetters[columnName](this);

                // run validation
                var results = new List<ValidationResult>();
                var vc = new ValidationContext(this, null, null) {MemberName = columnName};
                Validator.TryValidateProperty(value, vc, results);

                // transpose results
                string[] errors = Array.ConvertAll(results.ToArray(), o => o.ErrorMessage);
                return string.Join(Environment.NewLine, errors);
            }
            return string.Empty;
        }

        private static Dictionary<string, Func<object, object>> GetPropertyGetterLookups(Type objType)
        {
            string key = objType.FullName ?? "";
            if (!PropertyLookupCache.ContainsKey(key))
            {
                Dictionary<string, Func<object, object>> o = objType.GetProperties()
                                                                    .Where(p => GetValidations(p).Length != 0)
                                                                    .ToDictionary(p => p.Name, CreatePropertyGetter);

                PropertyLookupCache[key] = o;
                return o;
            }
            return (Dictionary<string, Func<object, object>>) PropertyLookupCache[key];
        }

        private static Func<object, object> CreatePropertyGetter(PropertyInfo propertyInfo)
        {
            ParameterExpression instanceParameter = Expression.Parameter(typeof (object), "instance");

            Expression<Func<object, object>> expression = Expression.Lambda<Func<object, object>>(
                Expression.ConvertChecked(
                    Expression.MakeMemberAccess(
                        Expression.ConvertChecked(instanceParameter, propertyInfo.DeclaringType),
                        propertyInfo),
                    typeof (object)),
                instanceParameter);

            Func<object, object> compiledExpression = expression.Compile();

            return compiledExpression;
        }

        private static ValidationAttribute[] GetValidations(PropertyInfo property)
        {
            return (ValidationAttribute[]) property.GetCustomAttributes(typeof (ValidationAttribute), true);
        }
    }
}